/** @file
  Read EDID information and parse EDID information.

  Copyright (c) 2008 - 2014, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "CirrusLogic5430.h"
#include "CirrusLogic5430I2c.h"

//
// EDID block
//
typedef struct {
  UINT8   Header[8];                        //EDID header "00 FF FF FF FF FF FF 00"
  UINT16  ManufactureName;                  //EISA 3-character ID
  UINT16  ProductCode;                      //Vendor assigned code
  UINT32  SerialNumber;                     //32-bit serial number
  UINT8   WeekOfManufacture;                //Week number
  UINT8   YearOfManufacture;                //Year
  UINT8   EdidVersion;                      //EDID Structure Version
  UINT8   EdidRevision;                     //EDID Structure Revision
  UINT8   VideoInputDefinition;
  UINT8   MaxHorizontalImageSize;           //cm
  UINT8   MaxVerticalImageSize;             //cm
  UINT8   DisplayTransferCharacteristic;
  UINT8   FeatureSupport;
  UINT8   RedGreenLowBits;                  //Rx1 Rx0 Ry1 Ry0 Gx1 Gx0 Gy1Gy0
  UINT8   BlueWhiteLowBits;                 //Bx1 Bx0 By1 By0 Wx1 Wx0 Wy1 Wy0
  UINT8   RedX;                             //Red-x Bits 9 - 2
  UINT8   RedY;                             //Red-y Bits 9 - 2
  UINT8   GreenX;                           //Green-x Bits 9 - 2
  UINT8   GreenY;                           //Green-y Bits 9 - 2
  UINT8   BlueX;                            //Blue-x Bits 9 - 2
  UINT8   BlueY;                            //Blue-y Bits 9 - 2
  UINT8   WhiteX;                           //White-x Bits 9 - 2
  UINT8   WhiteY;                           //White-x Bits 9 - 2
  UINT8   EstablishedTimings[3];
  UINT8   StandardTimingIdentification[16];
  UINT8   DetailedTimingDescriptions[72];
  UINT8   ExtensionFlag;                    //Number of (optional) 128-byte EDID extension blocks to follow
  UINT8   Checksum;
} EDID_BLOCK;

#define EDID_BLOCK_SIZE                        128
#define VBE_EDID_ESTABLISHED_TIMING_MAX_NUMBER 17

typedef struct {
  UINT16  HorizontalResolution;
  UINT16  VerticalResolution;
  UINT16  RefreshRate;
} EDID_TIMING;

typedef struct {
  UINT32  ValidNumber;
  UINT32  Key[VBE_EDID_ESTABLISHED_TIMING_MAX_NUMBER];
} VALID_EDID_TIMING;

//
// Standard timing defined by VESA EDID
//
EDID_TIMING mVbeEstablishedEdidTiming[] = {
  //
  // Established Timing I
  //
  {800, 600, 60},
  {800, 600, 56},
  {640, 480, 75},
  {640, 480, 72},
  {640, 480, 67},
  {640, 480, 60},
  {720, 400, 88},
  {720, 400, 70},
  //
  // Established Timing II
  //
  {1280, 1024, 75},
  {1024,  768, 75},
  {1024,  768, 70},
  {1024,  768, 60},
  {1024,  768, 87},
  {832,   624, 75},
  {800,   600, 75},
  {800,   600, 72},
  //
  // Established Timing III
  //
  {1152, 870, 75}
};

/**
  Read EDID information from I2C Bus on CirrusLogic.

  @param  Private             Pointer to CIRRUS_LOGIC_5430_PRIVATE_DATA.
  @param  EdidDataBlock       Pointer to EDID data block.
  @param  EdidSize            Returned EDID block size.

  @retval EFI_UNSUPPORTED
  @retval EFI_SUCCESS

**/
EFI_STATUS
ReadEdidData (
  CIRRUS_LOGIC_5430_PRIVATE_DATA     *Private,
  UINT8                              **EdidDataBlock,
  UINTN                              *EdidSize
  )
{
  UINTN             Index;
  UINT8             EdidData[EDID_BLOCK_SIZE * 2];
  UINT8             *ValidEdid;
  UINT64            Signature;

  for (Index = 0; Index < EDID_BLOCK_SIZE * 2; Index ++) {
    I2cReadByte (Private->PciIo, 0xa0, (UINT8)Index, &EdidData[Index]);
  }

  //
  // Search for the EDID signature
  //
  ValidEdid = &EdidData[0];
  Signature = 0x00ffffffffffff00ull;
  for (Index = 0; Index < EDID_BLOCK_SIZE * 2; Index ++, ValidEdid ++) {
    if (CompareMem (ValidEdid, &Signature, 8) == 0) {
      break;
    }
  }

  if (Index == 256) {
    //
    // No EDID signature found
    //
    return EFI_UNSUPPORTED;
  }

  *EdidDataBlock = AllocateCopyPool (
                     EDID_BLOCK_SIZE,
                     ValidEdid
                     );
  if (*EdidDataBlock == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Currently only support EDID 1.x
  //
  *EdidSize = EDID_BLOCK_SIZE;

  return EFI_SUCCESS;
}

/**
  Generate a search key for a specified timing data.

  @param  EdidTiming             Pointer to EDID timing

  @return The 32 bit unique key for search.

**/
UINT32
CalculateEdidKey (
  EDID_TIMING       *EdidTiming
  )
{
  UINT32 Key;

  //
  // Be sure no conflicts for all standard timing defined by VESA.
  //
  Key = (EdidTiming->HorizontalResolution * 2) + EdidTiming->VerticalResolution;
  return Key;
}

/**
  Search a specified Timing in all the valid EDID timings.

  @param  ValidEdidTiming        All valid EDID timing information.
  @param  EdidTiming             The Timing to search for.

  @retval TRUE                   Found.
  @retval FALSE                  Not found.

**/
BOOLEAN
SearchEdidTiming (
  VALID_EDID_TIMING *ValidEdidTiming,
  EDID_TIMING       *EdidTiming
  )
{
  UINT32 Index;
  UINT32 Key;

  Key = CalculateEdidKey (EdidTiming);

  for (Index = 0; Index < ValidEdidTiming->ValidNumber; Index ++) {
    if (Key == ValidEdidTiming->Key[Index]) {
      return TRUE;
    }
  }

  return FALSE;
}

/**
  Parse the Established Timing and Standard Timing in EDID data block.

  @param  EdidBuffer             Pointer to EDID data block
  @param  ValidEdidTiming        Valid EDID timing information

  @retval TRUE                   The EDID data is valid.
  @retval FALSE                  The EDID data is invalid.

**/
BOOLEAN
ParseEdidData (
  UINT8                         *EdidBuffer,
  VALID_EDID_TIMING             *ValidEdidTiming
  )
{
  UINT8        CheckSum;
  UINT32       Index;
  UINT32       ValidNumber;
  UINT32       TimingBits;
  UINT8        *BufferIndex;
  UINT16       HorizontalResolution;
  UINT16       VerticalResolution;
  UINT8        AspectRatio;
  UINT8        RefreshRate;
  EDID_TIMING  TempTiming;
  EDID_BLOCK   *EdidDataBlock;

  EdidDataBlock = (EDID_BLOCK *) EdidBuffer;

  //
  // Check the checksum of EDID data
  //
  CheckSum = 0;
  for (Index = 0; Index < EDID_BLOCK_SIZE; Index ++) {
    CheckSum = (UINT8) (CheckSum + EdidBuffer[Index]);
  }
  if (CheckSum != 0) {
    return FALSE;
  }

  ValidNumber = 0;
  SetMem (ValidEdidTiming, sizeof (VALID_EDID_TIMING), 0);

  if ((EdidDataBlock->EstablishedTimings[0] != 0) ||
      (EdidDataBlock->EstablishedTimings[1] != 0) ||
      (EdidDataBlock->EstablishedTimings[2] != 0)
      ) {
    //
    // Established timing data
    //
    TimingBits = EdidDataBlock->EstablishedTimings[0] |
                 (EdidDataBlock->EstablishedTimings[1] << 8) |
                 ((EdidDataBlock->EstablishedTimings[2] & 0x80) << 9) ;
    for (Index = 0; Index < VBE_EDID_ESTABLISHED_TIMING_MAX_NUMBER; Index ++) {
      if (TimingBits & 0x1) {
        ValidEdidTiming->Key[ValidNumber] = CalculateEdidKey (&mVbeEstablishedEdidTiming[Index]);
        ValidNumber ++;
      }
      TimingBits = TimingBits >> 1;
    }
  } else {
    //
    // If no Established timing data, read the standard timing data
    //
    BufferIndex = &EdidDataBlock->StandardTimingIdentification[0];
    for (Index = 0; Index < 8; Index ++) {
      if ((BufferIndex[0] != 0x1) && (BufferIndex[1] != 0x1)){
        //
        // A valid Standard Timing
        //
        HorizontalResolution = (UINT16) (BufferIndex[0] * 8 + 248);
        AspectRatio = (UINT8) (BufferIndex[1] >> 6);
        switch (AspectRatio) {
          case 0:
            VerticalResolution = (UINT16) (HorizontalResolution / 16 * 10);
            break;
          case 1:
            VerticalResolution = (UINT16) (HorizontalResolution / 4 * 3);
            break;
          case 2:
            VerticalResolution = (UINT16) (HorizontalResolution / 5 * 4);
            break;
          case 3:
            VerticalResolution = (UINT16) (HorizontalResolution / 16 * 9);
            break;
          default:
            VerticalResolution = (UINT16) (HorizontalResolution / 4 * 3);
            break;
        }
        RefreshRate = (UINT8) ((BufferIndex[1] & 0x1f) + 60);
        TempTiming.HorizontalResolution = HorizontalResolution;
        TempTiming.VerticalResolution = VerticalResolution;
        TempTiming.RefreshRate = RefreshRate;
        ValidEdidTiming->Key[ValidNumber] = CalculateEdidKey (&TempTiming);
        ValidNumber ++;
      }
      BufferIndex += 2;
    }
  }

  ValidEdidTiming->ValidNumber = ValidNumber;
  return TRUE;
}

/**
  Construct the valid video modes for CirrusLogic5430.

**/
EFI_STATUS
CirrusLogic5430VideoModeSetup (
  CIRRUS_LOGIC_5430_PRIVATE_DATA  *Private
  )
{
  EFI_STATUS                             Status;
  UINT32                                 Index;
  BOOLEAN                                EdidFound;
  EFI_EDID_OVERRIDE_PROTOCOL             *EdidOverride;
  UINT32                                 EdidAttributes;
  BOOLEAN                                EdidOverrideFound;
  UINTN                                  EdidOverrideDataSize;
  UINT8                                  *EdidOverrideDataBlock;
  UINTN                                  EdidDiscoveredDataSize;
  UINT8                                  *EdidDiscoveredDataBlock;
  UINTN                                  EdidActiveDataSize;
  UINT8                                  *EdidActiveDataBlock;
  VALID_EDID_TIMING                      ValidEdidTiming;
  UINT32                                 ValidModeCount;
  CIRRUS_LOGIC_5430_MODE_DATA            *ModeData;
  BOOLEAN                                TimingMatch;
  CIRRUS_LOGIC_5430_VIDEO_MODES          *VideoMode;
  EDID_TIMING                            TempTiming;

  //
  // setup EDID information
  //
  Private->EdidDiscovered.Edid       = NULL;
  Private->EdidDiscovered.SizeOfEdid = 0;
  Private->EdidActive.Edid           = NULL;
  Private->EdidActive.SizeOfEdid     = 0;

  EdidFound               = FALSE;
  EdidOverrideFound       = FALSE;
  EdidAttributes          = 0xff;
  EdidOverrideDataSize    = 0;
  EdidOverrideDataBlock   = NULL;
  EdidActiveDataSize      = 0;
  EdidActiveDataBlock     = NULL;
  EdidDiscoveredDataBlock = NULL;

  //
  // Find EDID Override protocol firstly, this protocol is installed by platform if needed.
  //
  Status = gBS->LocateProtocol (
                   &gEfiEdidOverrideProtocolGuid,
                   NULL,
                   (VOID **) &EdidOverride
                   );
  if (!EFI_ERROR (Status)) {
    //
    // Allocate double size of VESA_BIOS_EXTENSIONS_EDID_BLOCK_SIZE to avoid overflow
    //
    EdidOverrideDataBlock = AllocatePool (EDID_BLOCK_SIZE * 2);
    if (NULL == EdidOverrideDataBlock) {
  		Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    Status = EdidOverride->GetEdid (
                             EdidOverride,
                             Private->Handle,
                             &EdidAttributes,
                             &EdidOverrideDataSize,
                             (UINT8 **) &EdidOverrideDataBlock
                             );
    if (!EFI_ERROR (Status)  &&
         EdidAttributes == 0 &&
         EdidOverrideDataSize != 0) {
      //
      // Succeeded to get EDID Override Data
      //
      EdidOverrideFound = TRUE;
    }
  }

  if (EdidOverrideFound != TRUE || EdidAttributes == EFI_EDID_OVERRIDE_DONT_OVERRIDE) {
    //
    // If EDID Override data doesn't exist or EFI_EDID_OVERRIDE_DONT_OVERRIDE returned,
    // read EDID information through I2C Bus
    //
    if (ReadEdidData (Private, &EdidDiscoveredDataBlock, &EdidDiscoveredDataSize) == EFI_SUCCESS) {
      Private->EdidDiscovered.SizeOfEdid = (UINT32) EdidDiscoveredDataSize;
     	Private->EdidDiscovered.Edid = (UINT8 *) AllocateCopyPool (
                                                          EdidDiscoveredDataSize,
                                                          EdidDiscoveredDataBlock
     																										  );

      if (NULL == Private->EdidDiscovered.Edid) {
     	  Status = EFI_OUT_OF_RESOURCES;
        goto Done;
      }

      EdidActiveDataSize  = Private->EdidDiscovered.SizeOfEdid;
      EdidActiveDataBlock = Private->EdidDiscovered.Edid;

      EdidFound = TRUE;
    }
  }

  if (EdidFound != TRUE && EdidOverrideFound == TRUE) {
    EdidActiveDataSize  = EdidOverrideDataSize;
    EdidActiveDataBlock = EdidOverrideDataBlock;
    EdidFound = TRUE;
 	}

 	if (EdidFound == TRUE) {
    //
    // Parse EDID data structure to retrieve modes supported by monitor
    //
    if (ParseEdidData ((UINT8 *) EdidActiveDataBlock, &ValidEdidTiming) == TRUE) {
      //
      // Copy EDID Override Data to EDID Active Data
      //
      Private->EdidActive.SizeOfEdid = (UINT32) EdidActiveDataSize;
      Private->EdidActive.Edid = (UINT8 *) AllocateCopyPool (
                                             EdidActiveDataSize,
                                             EdidActiveDataBlock
                                             );
      if (NULL == Private->EdidActive.Edid) {
   		  Status = EFI_OUT_OF_RESOURCES;
        goto Done;
      }
    }
  } else {
    Private->EdidActive.SizeOfEdid = 0;
    Private->EdidActive.Edid = NULL;
    EdidFound = FALSE;
  }

  if (EdidFound) {
    //
    // Initialize the private mode data with the supported modes.
    //
    ValidModeCount = 0;
    ModeData = &Private->ModeData[0];
    VideoMode = &CirrusLogic5430VideoModes[0];
    for (Index = 0; Index < CIRRUS_LOGIC_5430_MODE_COUNT; Index++) {

      TimingMatch = TRUE;

      //
      // Check whether match with CirrusLogic5430 video mode
      //
      TempTiming.HorizontalResolution = (UINT16) VideoMode->Width;
      TempTiming.VerticalResolution   = (UINT16) VideoMode->Height;
      TempTiming.RefreshRate          = (UINT16) VideoMode->RefreshRate;
      if (SearchEdidTiming (&ValidEdidTiming, &TempTiming) != TRUE) {
        TimingMatch = FALSE;
      }

      //
      // Not export Mode 0x0 as GOP mode, this is not defined in spec.
      //
      if ((VideoMode->Width == 0) || (VideoMode->Height == 0)) {
        TimingMatch = FALSE;
      }

      if (TimingMatch) {
        ModeData->ModeNumber = Index;
        ModeData->HorizontalResolution          = VideoMode->Width;
        ModeData->VerticalResolution            = VideoMode->Height;
        ModeData->ColorDepth                    = VideoMode->ColorDepth;
        ModeData->RefreshRate                   = VideoMode->RefreshRate;

        ModeData ++;
        ValidModeCount ++;
      }

      VideoMode ++;
    }

    Private->MaxMode = ValidModeCount;

  } else {
    //
    // If EDID information wasn't found
    //
    ModeData = &Private->ModeData[0];
    VideoMode = &CirrusLogic5430VideoModes[0];
    for (Index = 0; Index < CIRRUS_LOGIC_5430_MODE_COUNT; Index ++) {
      ModeData->ModeNumber = Index;
      ModeData->HorizontalResolution          = VideoMode->Width;
      ModeData->VerticalResolution            = VideoMode->Height;
      ModeData->ColorDepth                    = VideoMode->ColorDepth;
      ModeData->RefreshRate                   = VideoMode->RefreshRate;

      ModeData ++ ;
      VideoMode ++;
    }
    Private->MaxMode = CIRRUS_LOGIC_5430_MODE_COUNT;
  }

  if (EdidOverrideDataBlock != NULL) {
    FreePool (EdidOverrideDataBlock);
  }

  return EFI_SUCCESS;

Done:
  if (EdidOverrideDataBlock != NULL) {
    FreePool (EdidOverrideDataBlock);
  }
  if (Private->EdidDiscovered.Edid != NULL) {
    FreePool (Private->EdidDiscovered.Edid);
  }
  if (Private->EdidDiscovered.Edid != NULL) {
    FreePool (Private->EdidActive.Edid);
  }

  return EFI_DEVICE_ERROR;
}
